package plugins

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"go-caipu/pkg/util"
	"io/fs"
	"path"
	"runtime"
	"strings"

	"gitee.com/go-caipu-team/go-caipu-plugin-sdk/backend"

	"go-caipu/pkg/plugins/backendplugin"
	"go-caipu/pkg/plugins/log"
)

var (
	ErrInstallCorePlugin = errors.New("cannot install a Core plugin")
	ErrFileNotExist      = errors.New("file does not exist")
	ErrPluginFileRead    = errors.New("file could not be read")
)

type Plugin struct {
	JSONData

	FS    FS
	Class Class

	Parent   *Plugin
	Children []*Plugin

	// Signature fields
	Signature      SignatureStatus
	SignatureError *SignatureError

	Module  string
	BaseURL string
	log     log.Logger

	client backendplugin.Plugin
}

type PluginDTO struct {
	JSONData

	fs                FS
	logger            log.Logger
	supportsStreaming bool

	Class Class

	//
	// Signature fields
	Signature      SignatureStatus
	SignatureType  SignatureType
	SignatureOrg   string
	SignatureError *SignatureError

	// SystemJS fields
	Module  string
	BaseURL string
}
type JSONData struct {
	//Common Settings
	ID string `json:"id"`
	//Common Settings
	Name string `json:"name"`
	//Common Settings
	Type Type `json:"type"`
	//Common Settings
	Info         Info         `json:"info"`
	Dependencies Dependencies `json:"dependencies"`
	//是否是纯后端插件，还是纯前端插件，纯前端不加载后端核心程序
	Backend bool `json:"backend"`
	//后端插件是管理员插件，需要登录才能访问
	BackendAdmin bool `json:"backendAdmin"`

	//扩展用户信息
	XExtendUser bool     `json:"xExtendUser"`
	Routes      []*Route `json:"routes"`
	Tags        []string `json:"tags"` //关键词数组
	// Backend (Datasource + Renderer + SecretsManager)
	Executable string `json:"executable,omitempty"`
}
type Type string

var PluginTypes = []Type{
	DataSource,
	Panel,
	App,
	Ext,
}

// 插件类型 插件签名状态
// DataSource :后端插件，有数据源设置
// Panel      :前端插件，无数据源设置
// App        :应用插件，有数据源设置
// Ext         :扩展插件，无数据源设置
const (
	DataSource Type = "datasource"
	Panel      Type = "panel"
	App        Type = "app"
	Ext        Type = "ext"
)

func (pt Type) IsValid() bool {
	switch pt {
	case DataSource, Panel, App, Ext:
		return true
	}
	return false
}

type Info struct {
	Author      InfoLink      `json:"author"`
	Description string        `json:"description"`
	Links       []InfoLink    `json:"links"`
	Logos       Logos         `json:"logos"`
	Build       BuildInfo     `json:"build"`
	Screenshots []Screenshots `json:"screenshots"`
	Version     string        `json:"version"`
	Updated     string        `json:"updated"`
	Keywords    []string      `json:"keywords"` //关键词数组
}

type InfoLink struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

type Logos struct {
	Small string `json:"small"`
	Large string `json:"large"`
}

type Screenshots struct {
	Name string `json:"name"`
	Path string `json:"path"`
}

type StaticRoute struct {
	PluginID  string
	Directory string
}

// Dependencies 依赖组件
type Dependencies struct {
	CaiPuDependency string       `json:"caiPuDependency"`
	CaiPuVersion    string       `json:"caiPuVersion"`
	Plugins         []Dependency `json:"plugins"`
}
type Dependency struct {
	ID      string `json:"id"`
	Type    string `json:"type"`
	Name    string `json:"name"`
	Version string `json:"version"`
}
type BuildInfo struct {
	Time   int64  `json:"time,omitempty"`
	Repo   string `json:"repo,omitempty"`
	Branch string `json:"branch,omitempty"`
	Hash   string `json:"hash,omitempty"`
}

func (p *Plugin) PluginID() string {
	return p.ID
}
func (p *Plugin) Logger() log.Logger {
	return p.log
}
func (p *Plugin) SetLogger(l log.Logger) {
	p.log = l
}
func (p *Plugin) Start(ctx context.Context) error {
	if p.client == nil {
		return fmt.Errorf("could not start plugin %s as no plugin client exists", p.ID)
	}
	return p.client.Start(ctx)
}
func (p *Plugin) Stop(ctx context.Context) error {
	if p.client == nil {
		return nil
	}
	return p.client.Stop(ctx)
}

func (p *Plugin) IsDecommissioned() bool {
	if p.client != nil {
		return p.client.IsDecommissioned()
	}
	return false
}

// Decommission 关闭停止
func (p *Plugin) Decommission() error {
	if p.client != nil {
		return p.client.Decommission()
	}
	return nil
}
func (p *Plugin) Exited() bool {
	if p.client != nil {
		return p.client.Exited()
	}
	return false
}
func (p *Plugin) IsManaged() bool {
	if p.client != nil {
		return p.client.IsManaged()
	}
	return false
}

func (p *Plugin) Target() backendplugin.Target {
	if !p.Backend {
		return backendplugin.TargetNone
	}
	if p.client == nil {
		return backendplugin.TargetUnknown
	}
	return p.client.Target()
}

type PluginClient interface {
	backend.QueryDataHandler
	backend.CollectMetricsHandler
	backend.CheckHealthHandler
	backend.CallResourceHandler
	backend.StreamHandler
}

func (p *Plugin) ToDTO() PluginDTO {
	return PluginDTO{
		JSONData: p.JSONData,
		logger:   p.Logger(),
		fs:       p.FS,

		Class: p.Class,
		//Signature: p.Signature,
		//SignatureType: p.SignatureType,
		//SignatureOrg: p.SignatureOrg,
		SignatureError: p.SignatureError,
		Module:         p.Module,
		BaseURL:        p.BaseURL,
	}
}
func (p *Plugin) Client() (PluginClient, bool) {
	if p.client != nil {
		return p.client, true
	}
	return nil, false
}
func (p *Plugin) ExecutablePath() string {
	//if p.IsRenderer() {
	//	return p.executablePath("plugin_start")
	//}
	//
	//if p.IsSecretsManager() {
	//	return p.executablePath("secrets_plugin_start")
	//}

	return p.executablePath(p.Executable)
}

func (p *Plugin) executablePath(f string) string {
	os := strings.ToLower(runtime.GOOS)
	arch := runtime.GOARCH
	extension := ""

	if os == "windows" {
		extension = ".exe"
	}
	return path.Join(p.FS.Base(), fmt.Sprintf("%s_%s_%s%s", f, os, strings.ToLower(arch), extension))
}

func (p *Plugin) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
	pluginClient, ok := p.Client()
	if !ok {
		return nil, backendplugin.ErrPluginUnavailable
	}
	return pluginClient.QueryData(ctx, req)
}

func (p *Plugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
	pluginClient, ok := p.Client()
	if !ok {
		return backendplugin.ErrPluginUnavailable
	}
	return pluginClient.CallResource(ctx, req, sender)
}
func (p *Plugin) Execute(ctx context.Context, req *backend.ExecuteRequest) (*backend.ExecuteResponse, error) {
	pluginClient, ok := p.Client()
	if !ok {
		return nil, backendplugin.ErrPluginUnavailable
	}
	return pluginClient.Execute(ctx, req)
}
func (p *Plugin) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
	pluginClient, ok := p.Client()
	if !ok {
		return nil, backendplugin.ErrPluginUnavailable
	}
	return pluginClient.CheckHealth(ctx, req)
}

func (p *Plugin) CollectMetrics(ctx context.Context, req *backend.CollectMetricsRequest) (*backend.CollectMetricsResult, error) {
	pluginClient, ok := p.Client()
	if !ok {
		return nil, backendplugin.ErrPluginUnavailable
	}
	return pluginClient.CollectMetrics(ctx, req)
}

func (p *Plugin) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) {
	pluginClient, ok := p.Client()
	if !ok {
		return nil, backendplugin.ErrPluginUnavailable
	}
	return pluginClient.SubscribeStream(ctx, req)
}

func (p *Plugin) PublishStream(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) {
	pluginClient, ok := p.Client()
	if !ok {
		return nil, backendplugin.ErrPluginUnavailable
	}
	return pluginClient.PublishStream(ctx, req)
}

func (p *Plugin) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
	pluginClient, ok := p.Client()
	if !ok {
		return backendplugin.ErrPluginUnavailable
	}
	return pluginClient.RunStream(ctx, req, sender)
}
func (p *Plugin) RegisterClient(c backendplugin.Plugin) {
	p.client = c
}

func (p *Plugin) IsExternalPlugin() bool {
	return p.Class == External
}

type Class string

const (
	Core     Class = "core"
	Bundled  Class = "bundled"
	External Class = "external"
)

func (c Class) String() string {
	return string(c)
}
func (p *Plugin) IsCorePlugin() bool {
	return p.Class == Core
}

type PluginSource interface {
	PluginClass(ctx context.Context) Class
	PluginURIs(ctx context.Context) []string
	DefaultSignature(ctx context.Context) (Signature, bool)
}

func (p *Plugin) File(name string) (fs.File, error) {
	cleanPath, err := util.CleanRelativePath(name)
	if err != nil {
		// CleanRelativePath should clean and make the path relative so this is not expected to fail
		return nil, err
	}

	if p.FS == nil {
		return nil, ErrFileNotExist
	}

	f, err := p.FS.Open(cleanPath)
	if err != nil {
		return nil, err
	}

	return f, nil
}

type DuplicateError struct {
	PluginID string
}

func (e DuplicateError) Error() string {
	return fmt.Sprintf("plugin with ID '%s' already exists", e.PluginID)
}

func (e DuplicateError) Is(err error) bool {
	// nolint:errorlint
	_, ok := err.(DuplicateError)
	return ok
}

type Signature struct {
	Status     SignatureStatus
	Type       SignatureType
	SigningOrg string
}
type SignatureType string

const (
	GrafanaSignature     SignatureType = "caipu"
	CommercialSignature  SignatureType = "commercial"
	CommunitySignature   SignatureType = "community"
	PrivateSignature     SignatureType = "private"
	PrivateGlobSignature SignatureType = "private-glob"
)

func (s SignatureType) IsValid() bool {
	switch s {
	case GrafanaSignature, CommercialSignature, CommunitySignature, PrivateSignature, PrivateGlobSignature:
		return true
	}
	return false
}

type FS interface {
	fs.FS

	Base() string
	Files() []string
}

type FoundBundle struct {
	Primary  FoundPlugin
	Children []*FoundPlugin
}

type FoundPlugin struct {
	JSONData JSONData
	FS       FS
}

// Route describes a plugin route that is defined in
// the plugin.json file for a plugin.
type Route struct {
	Path         string          `json:"path"`
	Method       string          `json:"method"`
	URL          string          `json:"url"`
	URLParams    []URLParam      `json:"urlParams"`
	Headers      []Header        `json:"headers"`
	AuthType     string          `json:"authType"`
	TokenAuth    *JWTTokenAuth   `json:"tokenAuth"`
	JwtTokenAuth *JWTTokenAuth   `json:"jwtTokenAuth"`
	Body         json.RawMessage `json:"body"`
}

// Header describes an HTTP header that is forwarded with
// the proxied request for a plugin route
type Header struct {
	Name    string `json:"name"`
	Content string `json:"content"`
}

// URLParam describes query string parameters for
// a url in a plugin route
type URLParam struct {
	Name    string `json:"name"`
	Content string `json:"content"`
}

// JWTTokenAuth struct is both for normal Token Auth and JWT Token Auth with
// an uploaded JWT file.
type JWTTokenAuth struct {
	Url    string            `json:"url"`
	Scopes []string          `json:"scopes"`
	Params map[string]string `json:"params"`
}
